using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using InfiniTec.Properties;

namespace InfiniTec.Data
{
    /// <summary>
    /// This class manages the various properties of an Item.
    /// </summary>
    public class PropertyCollection : ICollection<Property>
    {
        private readonly Dictionary<string, Property> _Properties;
        private readonly List<string> _RemovedProperties;

        public IList<string> RemovedProperties
        {
            get { return _RemovedProperties; }
        }

        /// <summary>
        /// Creates a new instance of this class.
        /// </summary>
        public PropertyCollection()
        {
            _Properties = new Dictionary<string, Property>(StringComparer.OrdinalIgnoreCase);
            _RemovedProperties = new List<string>();
        }

        /// <summary>
        /// Adds the item to the collection.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#")]
        public void Add(Property item)
        {
            _Properties[item.Name] = item;

            if (_RemovedProperties.Contains(item.Name))
            {
                _RemovedProperties.Remove(item.Name);
            }
        }

        /// <summary>
        /// Adds the specified properties to the collection.
        /// </summary>
        /// <param name="properties">The properties to add</param>
        public void AddRange(IEnumerable<Property> properties)
        {
            if (properties == null) throw new ArgumentNullException("properties");

            foreach (Property property in properties)
            {
                Add(property);
            }
        }

        /// <summary>
        /// Creates a new <see cref="Property"/> and adds it to the collection.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The name of the property, 
        /// including the namespace and the local name.</param>
        /// <param name="value">The value of the property</param>
        public void Add<T>(string name, T value)
        {
            Add(new Property<T>(name, value));
        }

        /// <summary>
        /// Creates a new <see cref="Property"/> and adds it to the collection.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The name of the property, 
        /// including the namespace and the local name.</param>
        /// <param name="value">The value of the property</param>
        public void Add<T>(PropertyDefinition<T> propertyDefinition, T value)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            Add(propertyDefinition.Name, value);
        }


     

        /// <summary>
        /// Creates a new <see cref="Property"/> and adds it to the collection.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The name of the property, 
        /// including the namespace and the local name.</param>
        /// <param name="values">The value of the property</param>
        public void AddMultiValued<T>(string name, T[] values)
        {
            Add(new MultiValuedProperty<T>(name, values));
        }

        /// <summary>
        /// Creates a new <see cref="Property"/> and adds it to the collection.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The name of the property, 
        /// including the namespace and the local name.</param>
        /// <param name="values">The value of the property</param>
        public void AddMultiValued<T>(PropertyDefinition<T> propertyDefinition, T[] values)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            Add(propertyDefinition.Name, values);
        }

        /// <summary>
        /// Creates a new <see cref="Property"/> and adds it to the collection.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The name of the property, 
        /// including the namespace and the local name.</param>
        /// <param name="values">The value of the property</param>
        public void AddMultiValued<T>(PropertyDefinition<T?> propertyDefinition, T?[] values)
            where T:struct
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            Add(propertyDefinition.Name, values);
        }


        /// <summary>
        /// Returns true, if the collection contains a <see cref="Property"/>
        /// with the same name as the specified <see cref="Property"/>
        /// </summary>
        /// <param name="item">The <see cref="Property"/> to search for.</param>
        /// <returns>True, if the collection contains another 
        /// property with the same name.</returns>
        public bool Contains(Property item)
        {
            if (item == null) throw new ArgumentNullException("item");

            return Contains(item.Name);
        }

        /// <summary>
        /// Returns true, if the collection contains a <see cref="Property"/>
        /// with the specified name
        /// </summary>
        /// <param name="name">The name of the property to search for.</param>
        /// <returns>True, if the collection contains another 
        /// property with the same name.</returns>
        public bool Contains(string name)
        {
            Property property;

            if (!_Properties.TryGetValue(name, out property)) return false;

            return property.Status != PropertyStatus.NotFound;
        }

        /// <summary>
        /// Returns true, if the collection contains a <see cref="Property"/>
        /// with the specified name
        /// </summary>
        /// <param name="propertyDefinition">The name of the property to search for.</param>
        /// <returns>True, if the collection contains another 
        /// property with the same name.</returns>
        public bool Contains<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");

            return Contains(propertyDefinition.Name);
        }

        /// <summary>
        /// Copyies the contents of this collection to the specified
        /// array.
        /// </summary>
        /// <param name="array">The destination array.</param>
        /// <param name="arrayIndex"></param>
        public void CopyTo(Property[] array, int arrayIndex)
        {
            _Properties.Values.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Removes the item with the same name from the collection.
        /// </summary>
        /// <param name="item">The item to remove</param>
        /// <returns>True, if the item was removed. False, otherwise.</returns>
        public bool Remove(Property item)
        {
            if (item == null) throw new ArgumentNullException("item");
            return Remove(item.Name);
        }

        /// <summary>
        /// Returns the <see cref="Property"/> with the specified name.
        /// </summary>
        /// <param name="name">The full qualified name of the property</param>
        /// <returns>The <see cref="Property"/> with the specified name.</returns>
        public Property this[string name]
        {
            get
            {
                Property property;

                if (!Contains(name))
                    throw new PropertyNotFoundException("The requested property was not found");

                property = _Properties[name];
                if (property.Status != PropertyStatus.Ok)
                {
                    switch (property.Status)
                    {
                        case PropertyStatus.NotAuthorized:
                            throw new UnauthorizedAccessException(string.Format(CultureInfo.CurrentUICulture, Resources.UnauthorizedPropertyAcessException, name));
                        case PropertyStatus.NotFound:
                            throw new PropertyNotFoundException(string.Format(CultureInfo.CurrentUICulture, Resources.PropertyNotFound, name));
                        default:
                            throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, Resources.CannotAccessThePropertyDueToUnknownError, name));
                    }
                }
                return property;
            }
        }

        /// <summary>
        /// Returns the <see cref="Property"/> with the specified name.
        /// </summary>
        /// <remarks>The requested property must be a single-valued property</remarks>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The full qualified name of the property</param>  
        /// <returns>The requested property</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public Property<T> GetProperty<T>(string name)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");

            Property p = EnsurePropertyIsResolved<T>(name, false);
            return (Property<T>) p;
        }

        /// <summary>
        /// Returns the <see cref="Property"/> with the specified name.
        /// </summary>
        /// <remarks>The requested property must be a single-valued property</remarks>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The definition of the property</param>  
        /// <returns>The requested property</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public Property<T> GetProperty<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetProperty<T>(propertyDefinition.Name);
        }


        /// <summary>
        /// Returns the <see cref="Property"/> with the specified name.
        /// </summary>
        /// <remarks>The requested property must be a single-valued property</remarks>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The full qualified name of the property</param>  
        /// <returns>The requested property</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public MultiValuedProperty<T> GetMultivaluedProperty<T>(string name)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");

            Property result = EnsurePropertyIsResolved<T>(name, true);

            return (MultiValuedProperty<T>) result;
        }

        private Property EnsurePropertyIsResolved<T>(string name, bool isMultivalued)
        {
            Property result = this[name];

            IPrototypeProperty prototype = result as IPrototypeProperty;

            if (prototype != null)
            {
                if (isMultivalued)
                {
                    result = prototype.ParseMultiValuedProperty<T>();
                }
                else
                {
                    result = prototype.ParseProperty<T>();
                }
                _Properties[name] = result;
            }

            //TODO: implement correct enum handling
            if (typeof(T).IsEnum && result is Property<int>)
            {
                result= new Property<T>(name, (T) (object) ((Property<int>) result).Value);
                _Properties[name] = result;
            }
            return result;
        }

        /// <summary>
        /// Returns the <see cref="Property"/> with the specified name.
        /// </summary>
        /// <remarks>The requested property must be a single-valued property</remarks>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The full qualified name of the property</param>  
        /// <returns>The requested property</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public MultiValuedProperty<T> GetMultivaluedProperty<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetMultivaluedProperty<T>(propertyDefinition.Name);
        }

        /// <summary>
        /// Tries to get the property with the specified name.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The full qualified name of the property</param>  
        /// <param name="result">When the method returns, contains the requested property, if found. Null, otherwise</param>
        /// <returns>True, if the property was found. False, otherwise</returns>
        public bool TryGetProperty<T>(string name, out Property<T> result)
        {
            Property prop;

            result = null;
            if (!Contains(name)) return false;

            prop = _Properties[name];
            if (prop.Status != PropertyStatus.Ok)
                return false;

            result = (Property<T>) EnsurePropertyIsResolved<T>(name, false);

            return true;
        }

        /// <summary>
        /// Tries to get the property with the specified name.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The property definition of the property</param>  
        /// <param name="property">When the method returns, contains the requested property, if found. Null, otherwise</param>
        /// <returns>True, if the property was found. False, otherwise</returns>
        public bool TryGetProperty<T>(PropertyDefinition<T> propertyDefinition, out Property<T> property)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return TryGetProperty(propertyDefinition.Name, out property);
        }

        /// <summary>
        /// Tries to get the multi-valued property with the specified name.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The full qualified name of the property</param>  
        /// <param name="property">When the method returns, contains the requested property, if found. Null, otherwise</param>
        /// <returns>True, if the property was found. False, otherwise</returns>
        public bool TryGetMultiValuedProperty<T>(string name, out MultiValuedProperty<T> property)
        {
            Property prop;

            property = null;
            if (!Contains(name)) return false;

            prop = _Properties[name];
            if (prop.Status !=
                PropertyStatus.Ok) return false;

            property = (MultiValuedProperty<T>) EnsurePropertyIsResolved<T>(name, true);


            return true;
        }

        /// <summary>
        /// Tries to get the multi-valued property with the specified name.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The full qualified name of the property</param>  
        /// <param name="property">When the method returns, contains the requested property, if found. Null, otherwise</param>
        /// <returns>True, if the property was found. False, otherwise</returns>
        public bool TryGetMultiValuedProperty<T>(PropertyDefinition<T> propertyDefinition, out MultiValuedProperty<T> property)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return TryGetMultiValuedProperty(propertyDefinition.Name, out property);
        }

        /// <summary>
        /// Copies the properties to the specified property collection.
        /// </summary>
        /// <param name="destination">The destination.</param>
        /// <param name="propertyNames">The property names.</param>
        /// <param name="markAsChanged"><see langword="true" />, if the properties should be marked as changed in the destination collection</param>
        public void CopyPropertiesTo(PropertyCollection destination, bool markAsChanged, params string[] propertyNames)
        {
            Property property;

            if (destination == null)
            {
                throw new ArgumentNullException("destination");
            }

            if (propertyNames == null)
            {
                return;
            }

            foreach (string propertyName in propertyNames)
            {
                if (Contains(propertyName))
                {
                    if (destination.Contains(propertyName))
                    {
                        destination.Remove(propertyName);
                    }
                    property = this[propertyName].Clone();
                    if (markAsChanged)
                    {
                        property.SetChanged();
                    }
                    destination.Add(property);
                }
            }
        }

        /// <summary>
        /// Copies the properties to the specified property collection.
        /// </summary>
        /// <param name="destination">The destination.</param>
        /// <param name="propertyNames">The property names.</param>
        /// <param name="markAsChanged"><see langword="true" />, if the properties should be marked as changed in the destination collection</param>
        public void CopyPropertiesTo(PropertyCollection destination, bool markAsChanged, params IPropertyDefinition[] propertyNames)
        {
            if (destination == null) throw new ArgumentNullException("destination");
            List<string> list = new List<string>();

            foreach (IPropertyDefinition propertyDefinition in propertyNames)
            {
                list.Add(propertyDefinition.Name);
            }

            CopyPropertiesTo(destination, markAsChanged, list.ToArray());
        }

        /// <summary>
        /// Removes the <see cref="Property"/> with the specified name
        /// from the collection.
        /// </summary>
        /// <param name="name">The full qualified name of the property</param>
        /// <returns>True, if the property was removed. False, otherwise.</returns>
        public bool Remove(string name)
        {
            _RemovedProperties.Add(name);

            return _Properties.Remove(name);
        }

        /// <summary>
        /// Removes the <see cref="Property"/> with the specified name
        /// from the collection.
        /// </summary>
        /// <param name="propertyDefinition">The full qualified name of the property</param>
        /// <returns>True, if the property was removed. False, otherwise.</returns>
        public bool Remove<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return Remove(propertyDefinition.Name);
        }

        /// <summary>
        /// Returns an <see cref="IEnumerator"/> which can be used to iterate
        /// through the collection of properties.
        /// </summary>
        /// <returns>An <see cref="IEnumerator"/> which can be used to iterate
        /// through the collection of properties</returns>
        IEnumerator<Property> IEnumerable<Property>.GetEnumerator()
        {
            return _Properties.Values.GetEnumerator();
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T GetPropertyValue<T>(string name)
        {
            return GetPropertyValue(name, default(T));
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The definition of the property.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T GetPropertyValue<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetPropertyValue<T>(propertyDefinition.Name);
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The definition of the property.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T? GetPropertyValue<T>(PropertyDefinition<T?> propertyDefinition)
            where T: struct
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetNullablePropertyValue<T>(propertyDefinition.Name);
        }


        /// <summary>
        /// Ensures that the multivalued property with the specified name exists.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The name of the property</param>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public void EnsureMultivaluedPropertyExists<T>(string name)
        {
            if (Contains(name)) return;

            Add(new MultiValuedProperty<T>(name));
        }

        /// <summary>
        /// Ensures that the multivalued property with the specified name exists.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The name of the property</param>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public void EnsurePropertyExists<T>(string name)
        {
            if (Contains(name)) return;

            Add(new Property<T>(name));
        }


        /// <summary>
        /// Ensures that the multivalued property with the specified name exists.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="propertyDefinition">The definition of the property</param>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public void EnsurePropertyExists<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");

            if (propertyDefinition.IsMultivalued) EnsureMultivaluedPropertyExists<T>(propertyDefinition.Name);
            else EnsurePropertyExists<T>(propertyDefinition.Name);
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T? GetNullablePropertyValue<T>(string name)
            where T : struct
        {
            Property<T> prop;

            if (!TryGetProperty(name, out prop)) return null;
            else return prop.Value;
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The name.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T? GetNullablePropertyValue<T>(PropertyDefinition<T> propertyDefinition)
            where T : struct
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetNullablePropertyValue<T>(propertyDefinition.Name);
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The name.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public T? GetNullablePropertyValue<T>(PropertyDefinition<T?> propertyDefinition)
            where T : struct
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetNullablePropertyValue<T>(propertyDefinition.Name);
        }

        /// <summary>
        /// Gets the multi-valued property value.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public IList<T> GetMultivaluedPropertyValue<T>(string name)
        {
            return GetMultivaluedPropertyValue<T>(name, null);
        }

        /// <summary>
        /// Gets the multi-valued property value.
        /// </summary>
        /// <param name="propertyDefinition">The name.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public IList<T> GetMultivaluedPropertyValue<T>(PropertyDefinition<T> propertyDefinition)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            return GetMultivaluedPropertyValue<T>(propertyDefinition.Name, null);
        }

        /// <summary>
        /// Gets the value of the property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="defaultValue">The default value for the property</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, specified
        /// default value is returned.</returns>
        public T GetPropertyValue<T>(string name, T defaultValue)
        {
            Property<T> prop;

            if (!TryGetProperty(name, out prop)) return defaultValue;
            else return prop.Value;
        }

        /// <summary>
        /// Gets the value of the property.
        /// </summary>
        /// <param name="propertyDefinition">The name of the property.</param>
        /// <param name="defaultValue">The default value for the property</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, specified
        /// default value is returned.</returns>
        public T GetPropertyValue<T>(PropertyDefinition<T> propertyDefinition, T defaultValue)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");

            return GetPropertyValue(propertyDefinition.Name, defaultValue);
        }

        /// <summary>
        /// Gets the value of the property.
        /// </summary>
        /// <param name="propertyDefinition">The name of the property.</param>
        /// <param name="defaultValue">The default value for the property</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, specified
        /// default value is returned.</returns>
        public T GetPropertyValue<T>(PropertyDefinition<T?> propertyDefinition, T defaultValue)
            where T:struct
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");

            return GetPropertyValue(propertyDefinition.Name, defaultValue);
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="defaultValue">The default value for the property</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the specified default
        /// value is returned.</returns>
        public IList<T> GetMultivaluedPropertyValue<T>(string name, T[] defaultValue)
        {
            MultiValuedProperty<T> prop;

            if (!TryGetMultiValuedProperty(name, out prop)) return defaultValue;
            else return prop.Values;
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The name of the property.</param>
        /// <param name="defaultValues">The default value for the property</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the specified default
        /// value is returned.</returns>
        public IList<T> GetMultivaluedPropertyValue<T>(PropertyDefinition<T> propertyDefinition, T[] defaultValues)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");

            return GetMultivaluedPropertyValue(propertyDefinition.Name, defaultValues);
        }

        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value of the property.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        public void SetPropertyValue<T>(string name, T value)
        {
            Property<T> prop;
            Property result;

            if (typeof(T).IsClass && ReferenceEquals(value, null)) Remove(name);

            if (TryGetProperty(name, out prop))
            {
                if (!EqualityComparer<T>.Default.Equals(prop.Value, value)) prop.Value = value;
                result = prop;
            }
            else
            {
                result = CreateProperty(name, value);
            }
            Add(result);
        }

        private Property CreateProperty<T>(string name, T value)
        {
            Type t = typeof(T);
            Type propertyType;

            propertyType = Nullable.GetUnderlyingType(t) ?? t;

            t = typeof(Property<>);
            t = t.MakeGenericType(propertyType);
            return (Property)Activator.CreateInstance(t, name, value);
        }


        /// <summary>
        /// Gets the property value.
        /// </summary>
        /// <param name="propertyDefinition">The name.</param>
        /// <param name="value">The vlaue of the value.</param>
        /// <typeparam name="T">The type of the requested property.</typeparam>
        /// <returns>The value of the requested property. If not found, the default
        /// value for the specified type is returned.</returns>
        public void SetPropertyValue<T>(PropertyDefinition<T> propertyDefinition, T value)
        {
            if (propertyDefinition == null) throw new ArgumentNullException("propertyDefinition");
            SetPropertyValue(propertyDefinition.Name, value);
        }

        /// <summary>
        /// Sets the nullable property value.
        /// </summary>
        /// <typeparam name="T">The type of the property</typeparam>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value of the property.</param>
        public void SetPropertyValue<T>(string name, T? value)
            where T : struct
        {
            Property<T> prop;

            if (value == null)
            {
                Remove(name);
                return;
            }

            if (!TryGetProperty(name, out prop))
            {
                prop = new Property<T>(name, value.Value);
                Add(prop);
            }
            else
            {
                prop.Value = value.Value;
            }
        }

        #region ICollection<Property> Members

        /// <summary>
        /// Removes all properties from the collection.
        /// </summary>
        public void Clear()
        {
            _RemovedProperties.AddRange(_Properties.Keys);
            _Properties.Clear();
        }

        /// <summary>
        /// Returns the number of properties in the collection.
        /// </summary>
        public int Count
        {
            get { return _Properties.Count; }
        }

        /// <summary>
        /// Returns false;
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an <see cref="IEnumerator"/> which can be used to iterate
        /// through the collection of properties.
        /// </summary>
        /// <returns>An <see cref="IEnumerator"/> which can be used to iterate
        /// through the collection of properties</returns>
        public IEnumerator GetEnumerator()
        {
            return _Properties.Values.GetEnumerator();
        }

        #endregion
    }
}